/**
 * 
 */
package org.chronologger.poppaea.client.widgets;

import org.chronologger.poppaea.client.Chronologger;
import org.chronologger.poppaea.client.Chronology;
import org.chronologger.poppaea.client.domain.Location;
import org.chronologger.poppaea.client.domain.Scene;
import org.chronologger.poppaea.client.overlap.OverlapGroup;
import org.chronologger.poppaea.client.overlap.OverlapResolver;
import org.chronologger.poppaea.client.overlap.Range;
import org.chronologger.poppaea.client.utils.Point;

import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;

public class SceneWidget extends Composite implements MouseEventHandler {

	private static final int SCROLLBAR_MARGIN = 15;

	private static final long serialVersionUID = 1L;

	private static final int RESIZE_AREA_WIDTH = 8;
	public static final int MOVEMENT_STEP = 10;

	protected enum DragMode {
		none, move, resize
	};

	protected final EventAwareAbsolutePanel panel = new EventAwareAbsolutePanel();
	protected final Chronology owner;
	protected final Scene scene;
	protected final Label titleLabel = new Label();
	protected HandlerRegistration onEditorCloseRegistration;

	protected Point dragStart = new Point();
	protected DragMode dragMode = DragMode.none;
	protected Long originalStartTime, originalEndTime;
	protected int originalLocationId;
	protected int currentLocationId;
	protected boolean mouseOver = false;
	protected FocusWidget focusedInput = null;
	protected boolean restartDrag = false;

	public SceneWidget(final Scene scene, final Chronology owner) {
		this.scene = scene;
		this.owner = owner;
		scene.setWidget(this);

		owner.getOverlapResolver(scene.getLocation()).addRange(scene);

		initWidget(panel);
		panel.setStyleName("widget-event");
		panel.add(titleLabel, 3, 0);
		titleLabel.setStyleName("widget-event-title");

		panel.addEventHandler(Event.ONDBLCLICK, new EventHandler() {

			@Override
			public void fire(Event event) {
				showEditor();
				ensureVisibility();
				event.stopPropagation();
				event.preventDefault();
			}
		});
		EventHandler.setupMouseHandlers(panel, this);

		updateGroup();
		updateFromScene();
	}

	protected void updateFromScene() {
		titleLabel.setText(scene.getTitle());
		getElement().getStyle()
				.setProperty("backgroundColor", scene.getColor());
	}

	public void showEditor() {
		SceneEditor.getInstance().setEvent(scene);
		onEditorCloseRegistration = SceneEditor.getInstance().addCloseHandler(
				new CloseHandler<PopupPanel>() {
					public void onClose(CloseEvent<PopupPanel> event) {
						updateFromScene();
						scene.updateOnServer();
						onEditorCloseRegistration.removeHandler();
					}
				});
	}

	public void onMouseDown(Event event) {
		// start drag and drop
		event.stopPropagation();
		event.preventDefault();
		// set capture
		DOM.setCapture(getElement());

		// ensureVisibility(thePoint.x);

		originalStartTime = scene.getStartTime();
		originalEndTime = scene.getEndTime();
		currentLocationId = originalLocationId = owner.getLocations().indexOf(
				scene.getLocation());
		dragStart = owner.clientToScenesPanelPosition(new Point(event));

		if (event.getClientX() - getAbsoluteLeft() >= getOffsetWidth()
				- RESIZE_AREA_WIDTH) {
			dragMode = DragMode.resize;
		} else {
			dragMode = DragMode.move;
		}

		restartDrag = false;

		// ensure visibility
		new Timer() {
			@Override
			public void run() {
				// we put this in a timer in order to receive any double click
				// events that may occur
				ensureVisibilityWithZoom();
				if (SceneEditor.getInstance().getScene() == scene)
					SceneEditor.getInstance().setEvent(scene);
				// use the next mouse-move position to restart dragging
				restartDrag = true;
			}
		}.schedule(200);

	}

	private boolean handleMoveEvent(int offset, Point currentPos,
			OverlapResolver overlapResolver) {
		if (restartDrag) {
			dragStart = currentPos;
			restartDrag = false;
		}

		// drag
		// calculate new start time
		Long newStartTime = owner.getPositionResolver()
				.getTime(
						(owner.getPositionResolver().getPosition(
								originalStartTime) + MOVEMENT_STEP / 2)
								/ MOVEMENT_STEP * MOVEMENT_STEP + offset);

		// update location, if necessary
		int newLocationId = currentPos.y
				/ (Chronology.LOCATION_WIDGET_GAP + LocationWidget.LOCATION_WIDGET_HEIGHT);

		if (newLocationId < 0)
			newLocationId = 0;
		if (newLocationId >= owner.getLocations().size())
			newLocationId = owner.getLocations().size() - 1;

		if (newStartTime != scene.getStartTime()
				|| newLocationId != currentLocationId) {
			// and end time
			scene.setEndTime(scene.getEndTime() - scene.getStartTime()
					+ newStartTime);
			scene.setStartTime(newStartTime);

			if (newLocationId != currentLocationId) {
				overlapResolver.removeRange(scene);
				updateGroup();
				Location newLocation = owner.getLocations().get(newLocationId);
				scene.setLocation(newLocation);
				currentLocationId = newLocationId;
				owner.getOverlapResolver(newLocation).addRange(scene);
			} else {
				overlapResolver.updateRange(scene);
			}
			updateGroup();

			return true;
		}

		return false;
	}

	private boolean handleResizeEvent(int offset,
			OverlapResolver overlapResolver, boolean changed) {
		final int endTimePos = owner.getPositionResolver().getPosition(
				originalEndTime);
		final int startTimePos = owner.getPositionResolver().getPosition(
				originalStartTime);
		int newPos = (endTimePos + MOVEMENT_STEP / 2) / MOVEMENT_STEP
				* MOVEMENT_STEP + offset;
		if (newPos < startTimePos + MOVEMENT_STEP)
			newPos = startTimePos + MOVEMENT_STEP;
		Long newEndTime = owner.getPositionResolver().getTime(newPos);

		if (scene.getEndTime() != newEndTime) {
			scene.setEndTime(newEndTime);

			overlapResolver.updateRange(scene);

			updateGroup();

			return true;
		}
		return false;
	}

	public void onMouseMove(Event event) {
		Point currentPos = owner.clientToScenesPanelPosition(new Point(event));
		int offset = (currentPos.x - dragStart.x) / MOVEMENT_STEP
				* MOVEMENT_STEP;
		OverlapResolver overlapResolver = owner.getOverlapResolver(scene
				.getLocation());

		boolean changed = false;

		switch (dragMode) {
		case move:
			changed = handleMoveEvent(offset, currentPos, overlapResolver);
			break;
		case resize:
			changed = handleResizeEvent(offset, overlapResolver, changed);
			break;
		case none:
			Chronologger.setGlobalCursor("move");
			if (event.getClientX() - getAbsoluteLeft() >= getOffsetWidth()
					- RESIZE_AREA_WIDTH) {
				Chronologger.setGlobalCursor("ew-resize");
			}
		}

		if (changed) {
			// update position of editor
			if (SceneEditor.getInstance().getScene() == scene)
				SceneEditor.getInstance().setEvent(scene);

			// ensure visibility
			new Timer() {
				@Override
				public void run() {
					ensureVisibility();
				}
			}.schedule(1);
		}

		// stop the event from reaching parent widget
		event.stopPropagation();
		event.preventDefault();
	}

	public void ensureVisibilityWithZoom() {
		ScrollPanel view = owner.getScenesScrollPanel();

		// adjust zoom
		Long min = owner.getPositionResolver()
				.getTime(view.getScrollPosition());
		Long max = owner.getPositionResolver().getTime(
				view.getScrollPosition() + view.getOffsetWidth());
		Long total = (max - min) / 1000;

		int resolution = owner.getPositionResolver().getPreferredResolution();
		while (total / 2 < scene.getTo() - scene.getFrom()) {
			total *= 2;
			resolution /= 2;
		}
		if (resolution != owner.getPositionResolver().getPreferredResolution())
			owner.setPreferredResolution(resolution);

		// adjust horizontal and vertical scroll position
		ensureVisibility();
	}

	public void ensureVisibility() {
		ScrollPanel view = owner.getScenesScrollPanel();
		AbsolutePanel parent = owner.getScenesPanel();

		// Horizontal adjustment
		int minVisible = view.getHorizontalScrollPosition();
		int maxVisible = minVisible + view.getOffsetWidth() - SCROLLBAR_MARGIN;
		int minWanted = parent.getWidgetLeft(this);
		int maxWanted = minWanted + getOffsetWidth();

		if (maxVisible < maxWanted + 5) {
			view.setHorizontalScrollPosition(maxWanted + 20
					- view.getOffsetWidth());
		} else if (minVisible > minWanted - 5) {
			view.setHorizontalScrollPosition(minWanted - 5);
		}

		// Vertical adjustment
		minVisible = view.getScrollPosition();
		maxVisible = view.getScrollPosition() + view.getOffsetHeight()
				- SCROLLBAR_MARGIN;
		minWanted = parent.getWidgetTop(this);
		maxWanted = minWanted + getOffsetHeight();

		if (minVisible > minWanted - 30) {
			view.setScrollPosition(minWanted - 30);
		} else if (maxVisible < maxWanted + 30) {
			view.setScrollPosition(maxWanted + 45 - view.getOffsetHeight());
		}

		if (SceneEditor.getInstance().getScene() == scene)
			SceneEditor.getInstance().setEvent(scene);
	}

	private void updateGroup() {
		// update this widget group's position
		for (OverlapGroup group : owner.getOverlapResolver(scene.getLocation())) {
			for (Range groupScene : group) {
				owner.updateSceneWidgetPosition(((Scene) groupScene)
						.getWidget());
			}
		}
	}

	public void onMouseUp(Event event) {
		// release capture, stop dragging
		DOM.releaseCapture(getElement());
		dragMode = DragMode.none;
		event.stopPropagation();
		event.preventDefault();

		scene.updateOnServer();

		int clientX = event.getClientX() - getAbsoluteLeft();
		int clientY = event.getClientY() - getAbsoluteTop();
		if (clientX >= 0 && clientX <= getOffsetWidth() && clientY >= 0
				&& clientY <= getOffsetHeight()) {
			if (clientX > getOffsetWidth() - RESIZE_AREA_WIDTH)
				Chronologger.setGlobalCursor("ew-resize");
			else
				Chronologger.setGlobalCursor("move");
		} else {
			Chronologger.setGlobalCursor("");
		}
	}

	public void onMouseOut(Event event) {
		mouseOver = false;
		if (dragMode == DragMode.none)
			Chronologger.setGlobalCursor("");
	}

	public void onMouseOver(Event event) {
		mouseOver = true;
	}

	public Scene getScene() {
		return scene;
	}
}